package com.naiterui.ehp.bs.esign.utils;

import cn.hutool.core.collection.CollUtil;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Chunk;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfDocument;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.text.pdf.draw.LineSeparator;
import com.naiterui.ehp.bp.bo.emr.MedicalRecordSettingBO;
import com.naiterui.ehp.bp.constants.MedicalRecordSettingEnum;
import com.naiterui.ehp.bp.utils.date.DateUtil;
import com.naiterui.ehp.bp.utils.lang.ConfigUtil;
import com.naiterui.ehp.bs.esign.entity.mongo.MedicalRecord;
import lombok.Builder;
import lombok.Data;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author guoyongxiang
 * @date 2019/9/10 20:05
 * @since 1.0.0
 */
@Component
public class EsignEmrPdfUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(EsignEmrPdfUtil.class);

    private static final String HOSPITAL_SEAL_PATH = ConfigUtil.getString("prescription.seal.path");
    private static final String PDF_FILE_BASE_PATH = ConfigUtil.getString("emr.file.pdf.path");
    @Value("${file.base.prefix}")
    private String basePath;

    BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
    BaseFont bfEnglish = BaseFont.createFont(BaseFont.HELVETICA, "", BaseFont.NOT_EMBEDDED);

    Font firsetTitleFont = new Font(bfChinese, 22, Font.BOLD); // 一级标题
    Font secondTitleFont = new Font(bfChinese, 15, Font.BOLD); // 二级标题
    Font secondTitleFontEn = new Font(bfEnglish, 15, Font.BOLD); // 二级标题-EN
    Font fontContent = new Font(bfChinese, 8, Font.NORMAL);
    Font fontContent2 = new Font(bfChinese, 9, Font.BOLD, BaseColor.DARK_GRAY);
    Font fontContentBOLD = new Font(bfChinese, 9, Font.BOLD);
    Font fontSubContentBOLD = new Font(bfChinese, 11, Font.BOLD);
    Font fontSubContent = new Font(bfChinese, 11, Font.NORMAL);
    Font fontContentEn = new Font(bfEnglish, 8, Font.NORMAL);
    Font hideFont = new Font(bfChinese, 8, Font.NORMAL, BaseColor.WHITE.brighter());
    LineSeparator line = new LineSeparator(1.5f, 100, BaseColor.GRAY, Element.ALIGN_CENTER, -3f);
    LineSeparator line1 = new LineSeparator(1f, 95, BaseColor.GRAY, Element.ALIGN_CENTER, 3f);
    LineSeparator line2 = new LineSeparator(1f, 95, new BaseColor(238, 238, 238), Element.ALIGN_CENTER, -2f);

    public EsignEmrPdfUtil() throws IOException, DocumentException {}

    public String generatePDF(EmrBO emr) throws IOException, DocumentException, ReflectiveOperationException {
        long start = System.currentTimeMillis();
        String path = PDF_FILE_BASE_PATH + emr.getPatientId() + "/" + emr.getEmrId() + ".pdf";
        File file = new File(basePath + path);
        if (!file.exists()) {
            FileUtils.forceMkdirParent(file);
            FileOutputStream fos = new FileOutputStream(file);
            this.generatePDF(fos, emr);
            fos.close();
            LOGGER.info("病例PDF生成完成{} cost:{} ms, emrId:{}, path:{}", file.exists(), System.currentTimeMillis() - start,
                emr.getEmrId(), path);
        }
        return path;
    }

    private void generatePDF(OutputStream ouput, EmrBO emr)
        throws DocumentException, IOException, ReflectiveOperationException {
        // 获取病历设置
        List<MedicalRecordSettingBO> medicalRecordSettingList = emr.getMedicalRecordSettingList();
        Map<Integer, Integer> mrSettingMap =
            CollUtil.isEmpty(medicalRecordSettingList) ? new HashMap<>() : medicalRecordSettingList.stream().collect(
            Collectors.toMap(MedicalRecordSettingBO::getType, MedicalRecordSettingBO::getEnabled, (k1, k2) -> k1));
        // 页面大小
        Rectangle rect = new Rectangle(PageSize.A4);
        Document document = new Document(rect);
        // 创建PdfWriter对象
        PdfWriter writer = PdfWriter.getInstance(document, ouput);
        // 打开文档
        document.open();
        // 页边空白
        document.setMargins(20, 20, 30, 40);
        String doctorName = emr.doctorName;
        this.generatePDFHerder(document, emr);
        if (currentHeight(writer) > 500) {
            // 生成表尾
            this.generatePDFTail(document, doctorName, writer);
            document.newPage();
            // 生成表头
            this.generatePDFHerder(document, emr);
        }
        // 主诉
        Phrase chiefComplaintPhrase = this.buildPhrase("主诉：", "", fontContentBOLD, fontContent);
        Phrase chiefComplaintContent = this.buildPhrase("", StringUtils.defaultIfBlank(emr.getChiefComplaint(), "无"),
            fontContentBOLD, fontContent);
        document.add(this.buildTable(new float[] {0.1f, 0.9f}, 95, 5, 2, chiefComplaintPhrase, chiefComplaintContent));
        if (currentHeight(writer) > 500) {
            // 生成表尾
            this.generatePDFTail(document, doctorName, writer);
            document.newPage();
            // 生成表头
            this.generatePDFHerder(document, emr);
        }
        // 现病史
        Integer hpiEnable = mrSettingMap.getOrDefault(MedicalRecordSettingEnum.TYPE_HPI.getType(), 1);
        // 病历设置开启则显示现病史
        if (hpiEnable.equals(MedicalRecordSettingEnum.ENABLE_YES)) {
            Phrase presentDiseasePhrase = this.buildPhrase("现病史：", "", fontContentBOLD, fontContent);
            Phrase presentDiseaseContent = this.buildPhrase("", StringUtils.defaultIfBlank(emr.getPresentDisease(), "无"), fontContentBOLD, fontContent);
            document.add(this.buildTable(new float[]{0.1f, 0.9f}, 95, 5, 2, presentDiseasePhrase, presentDiseaseContent));
        }
        if (currentHeight(writer) > 500) {
            // 生成表尾
            this.generatePDFTail(document, doctorName, writer);
            document.newPage();
            // 生成表头
            this.generatePDFHerder(document, emr);
        }
        // 既往史
        Integer phEnable =
            mrSettingMap.getOrDefault(MedicalRecordSettingEnum.TYPE_PAST_HISTORY.getType(), 1);
        // 病历设置开启则显示既往史
        if (phEnable.equals(MedicalRecordSettingEnum.ENABLE_YES)) {
            Phrase pastMedicalHistoryPhrase = this.buildPhrase("既往史：", "", fontContentBOLD, fontContent);
            Phrase pastMedicalHistoryContent = this.buildPhrase("",
                StringUtils.defaultIfBlank(emr.getPastMedicalHistory(), "无"), fontContentBOLD, fontContent);
            document.add(this.buildTable(new float[] {0.1f, 0.9f}, 95, 5, 2, pastMedicalHistoryPhrase,
                pastMedicalHistoryContent));
        }
        if (currentHeight(writer) > 500) {
            // 生成表尾
            this.generatePDFTail(document, doctorName, writer);
            document.newPage();
            // 生成表头
            this.generatePDFHerder(document, emr);
        }
        // 过敏史
        Integer ahEnable =
            mrSettingMap.getOrDefault(MedicalRecordSettingEnum.TYPE_ALLERGIC_HISTOR.getType(), 1);
        // 病历设置开启则显示过敏史
        if (ahEnable.equals(MedicalRecordSettingEnum.ENABLE_YES)) {
            Phrase allergyPhrase = this.buildPhrase("过敏史：", "", fontContentBOLD, fontContent);
            Phrase allergyContent =
                this.buildPhrase("", StringUtils.defaultIfBlank(emr.getAllergy(), "无"), fontContentBOLD, fontContent);
        document.add(this.buildTable(new float[] {0.1f, 0.9f}, 95, 5, 2, allergyPhrase, allergyContent));
    }
        if (currentHeight(writer) > 500) {
            // 生成表尾
            this.generatePDFTail(document, doctorName, writer);
            document.newPage();
            // 生成表头
            this.generatePDFHerder(document, emr);
        }
        // 家庭史
        Integer fhEnable =
            mrSettingMap.getOrDefault(MedicalRecordSettingEnum.TYPE_FAMILY_HISTORY.getType(), 1);
        // 病历设置开启则显示家庭史
        if (fhEnable.equals(MedicalRecordSettingEnum.ENABLE_YES)) {
            Phrase pastFamilyPhrase = this.buildPhrase("家庭史：", "", fontContentBOLD, fontContent);
            Phrase pastFamilyContent = this.buildPhrase("", StringUtils.defaultIfBlank(emr.getPastFamily(), "无"),
                fontContentBOLD, fontContent);
            document.add(this.buildTable(new float[] {0.1f, 0.9f}, 95, 5, 2, pastFamilyPhrase, pastFamilyContent));
        }
        if (currentHeight(writer) > 500) {
            // 生成表尾
            this.generatePDFTail(document, doctorName, writer);
            document.newPage();
            // 生成表头
            this.generatePDFHerder(document, emr);
        }
        // 月经史(性别为女展示)，并且病历设置开启则显示家庭史月经史
        Integer hmmcEnable = mrSettingMap.getOrDefault(MedicalRecordSettingEnum.TYPE_HMMC.getType(), 1);
        if (MedicalRecord.GenderEnum.GENDER_FEMALE.getId() == emr.getGender()
            && hmmcEnable.equals(MedicalRecordSettingEnum.ENABLE_YES)) {
            Phrase menstrualPhrase = this.buildPhrase("月经史：", "", fontContentBOLD, fontContent);
            Phrase menstrualContent =
                this.buildPhrase("", StringUtils.defaultIfBlank(emr.getMenstrual(), "无"), fontContentBOLD, fontContent);
            document.add(this.buildTable(new float[] {0.1f, 0.9f}, 95, 5, 2, menstrualPhrase, menstrualContent));
        }
        if (currentHeight(writer) > 500) {
            // 生成表尾
            this.generatePDFTail(document, doctorName, writer);
            document.newPage();
            // 生成表头
            this.generatePDFHerder(document, emr);
        }
        // 检查指标
        Integer iiEnable =
            mrSettingMap.getOrDefault(MedicalRecordSettingEnum.TYPE_INSPECTION_INDEX.getType(), 1);
        // 病历设置开启则显示检查指标
        if (iiEnable.equals(MedicalRecordSettingEnum.ENABLE_YES)) {
            Phrase checkParagraphPhrase = this.buildPhrase("检查指标：", "", fontContentBOLD, fontContent);
            String checkContent =
                String.join("；  ", "体温:" + (emr.getTemperature() == null ? "无" : emr.getTemperature() + " ℃"),
                    "体重:" + (emr.getWeight() == null ? "无" : emr.getWeight() + " kg"),
                    "心率:" + (emr.getHeartRate() == null ? "无" : emr.getHeartRate() + " bpm"),
                    "收缩压:" + (emr.getSystolicPressure() == null ? "无" : emr.getSystolicPressure() + " mmbhg"),
                    "舒张压:" + (emr.getDiastolicPressure() == null ? "无" : emr.getDiastolicPressure() + " mmbhg"));
            Phrase checkParagraphContent =
                this.buildPhrase("", StringUtils.defaultIfBlank(checkContent, "无"), fontContentBOLD, fontContent);
            document
                .add(this.buildTable(new float[] {0.1f, 0.9f}, 95, 5, 2, checkParagraphPhrase, checkParagraphContent));

            Phrase checkParagraphBlank = this.buildPhrase("", "", fontContentBOLD, fontContent);
            // 阳性体征
            Phrase checkPositiveSigns = this.buildPhrase("阳性体征：", "", fontContent, fontContent);
            Phrase checkPositiveSignsContent = this.buildPhrase("",
                StringUtils.defaultIfBlank(emr.getPositiveSigns(), "无"), fontContentBOLD, fontContent);
            document.add(this.buildTable(new float[] {0.1f, 0.9f}, 95, 5, 2, checkParagraphBlank, checkPositiveSigns));
            document.add(
                this.buildTable(new float[] {0.1f, 0.9f}, 95, 5, 2, checkParagraphBlank, checkPositiveSignsContent));

            // 必要的阴性体征
            Phrase negativeSigns = this.buildPhrase("必要的阴性体征：", "", fontContent, fontContent);
            Phrase negativeSignsContent = this.buildPhrase("", StringUtils.defaultIfBlank(emr.getNegativeSigns(), "无"),
                fontContentBOLD, fontContent);
            document.add(this.buildTable(new float[] {0.1f, 0.9f}, 95, 5, 2, checkParagraphBlank, negativeSigns));
            document
                .add(this.buildTable(new float[] {0.1f, 0.9f}, 95, 5, 2, checkParagraphBlank, negativeSignsContent));

            // 更多检查结果
            Phrase moreExamine = this.buildPhrase("更多检查结果：", "", fontContent, fontContent);
            Phrase moreExamineContent = this.buildPhrase("", StringUtils.defaultIfBlank(emr.getMoreExamine(), "无"),
                fontContentBOLD, fontContent);
            document.add(this.buildTable(new float[] {0.1f, 0.9f}, 95, 5, 2, checkParagraphBlank, moreExamine));
            document.add(this.buildTable(new float[] {0.1f, 0.9f}, 95, 5, 2, checkParagraphBlank, moreExamineContent));
        }
        // 诊断
        Phrase diagnosisPhrase = this.buildPhrase("诊断：", "", fontContentBOLD, fontContent);
        Phrase diagnosisContent = this.buildPhrase("",
            StringUtils.defaultIfBlank(String.join(",", emr.getDiagnosis()), "无"), fontContentBOLD, fontContent);
        document.add(this.buildTable(new float[] {0.1f, 0.9f}, 95, 5, 2, diagnosisPhrase, diagnosisContent));
        if (currentHeight(writer) > 500) {
            // 生成表尾
            this.generatePDFTail(document, doctorName, writer);
            document.newPage();
            // 生成表头
            this.generatePDFHerder(document, emr);
        }
        // 治疗意见
        Phrase treatmentOptionsPhrase = this.buildPhrase("治疗意见：", "", fontContentBOLD, fontContent);
        Phrase treatmentOptionsContent = this.buildPhrase("",
            StringUtils.defaultIfBlank(emr.getTreatmentOptions(), "无"), fontContentBOLD, fontContent);
        document
            .add(this.buildTable(new float[] {0.1f, 0.9f}, 95, 5, 2, treatmentOptionsPhrase, treatmentOptionsContent));

        if (emr.getReportImgUrls() != null && emr.getReportImgUrls().size() > 0) {
            for (String url : emr.getReportImgUrls()) {
                File imagefile = new File(url);
                if (imagefile.exists()) {
                    Image image = Image.getInstance(imagefile.getPath());
                    image.scaleToFit(80, 80);
                    document.add(image);
                }
            }
            document.add(line);
        }

        this.generatePDFTail(document, doctorName, writer);
        document.close();
        writer.close();
    }

    public static float currentHeight(PdfWriter writer) throws ReflectiveOperationException {
        Method getPdfDocument = writer.getClass().getDeclaredMethod("getPdfDocument");
        getPdfDocument.setAccessible(true);
        PdfDocument pdfD = (PdfDocument)getPdfDocument.invoke(writer);
        Field getHeight = pdfD.getClass().getDeclaredField("currentHeight");
        getHeight.setAccessible(true);
        return (float)getHeight.get(pdfD);
    }

    /**
     * 生成pdf头
     * 
     * @param document
     * @throws DocumentException
     * @throws IOException
     */
    private void generatePDFHerder(Document document, EmrBO emr) throws DocumentException, IOException {
        // title
        Paragraph titleRow = new Paragraph("门诊病历", firsetTitleFont);
        titleRow.setAlignment(PdfContentByte.ALIGN_CENTER);
        titleRow.setSpacingAfter(20);
        document.add(titleRow);

        // 分割线
        Paragraph lineParagraph = new Paragraph();
        lineParagraph.add(line);
        lineParagraph.setSpacingAfter(20);
        document.add(lineParagraph);

        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm");
        String createdTime = dateFormat.format(emr.getCreatedAt());

        Phrase name = this.buildPhrase("姓名：", emr.getName(), fontContentBOLD, fontContent);
        String genderStr = emr.getGender() == 0 ? "女" : "男";
        Phrase gender = this.buildPhrase("性别：", genderStr, fontContentBOLD, fontContent);
        Phrase birthday =
            this.buildPhrase("出生日期：", StringUtils.defaultIfBlank(emr.getBirthday(), "无"), fontContentBOLD, fontContent);
        Phrase age = this.buildPhrase("年龄：", emr.getAge(), fontContentBOLD, fontContent);
        Phrase phone =
            this.buildPhrase("手机号码：", StringUtils.defaultIfBlank(emr.getPhone(), "无"), fontContentBOLD, fontContent);
        Phrase hospital = this.buildPhrase("就诊医院：", emr.getHospitalName(), fontContentBOLD, fontContent);
        Phrase department = this.buildPhrase("科室：", emr.getDepartmentName(), fontContentBOLD, fontContent);
        Phrase time = this.buildPhrase("时间：", createdTime, fontContentBOLD, fontContentEn);

        // 第一行
        document.add(this.buildTable(new float[] {0.2f, 0.4f, 0.2f, 0.2f}, 95, 0, 0, name, gender, age, birthday));
        // 第二行
        document
            .add(this.buildTable(new float[] {0.2f, 0.4f, 0.2f, 0.2f}, 95, 0, 0, phone, hospital, department, time));
        document.add(line);
    }

    /**
     * 生成pdf尾
     * 
     * @param document
     * @throws DocumentException
     * @throws IOException
     */
    private void generatePDFTail(Document document, String doctorName, PdfWriter writer)
        throws DocumentException, IOException {
        Phrase doctor = this.buildPhrase("医生签章：", doctorName, fontSubContentBOLD, fontSubContent);
        Phrase doctorTime =
            this.buildPhrase("创建时间：", DateUtil.getCurrentFormatDateTime(), fontSubContentBOLD, fontSubContent);
        Phrase doctorBlank = this.buildPhrase("", "", fontSubContentBOLD, fontSubContent);

        PdfPTable doctorTable =
            this.buildTable(new float[] {0.65f, 0.35f}, 95, 2, 0, doctorBlank, doctor, doctorBlank, doctorTime);
        doctorTable.setTotalWidth(500f);
        doctorTable.writeSelectedRows(0, -1, 20, 150, writer.getDirectContent());

        // 设置医院图章
        File file = new File(HOSPITAL_SEAL_PATH);
        if (file.exists()) {
            Image image = Image.getInstance(HOSPITAL_SEAL_PATH);
            image.setAbsolutePosition(340, 100);
            image.scaleToFit(150, 150);
            document.add(image);
        } else {
            // LOGGER.warn("互联网医院图章不存在，请检查图章文件目录 {}", HOSPITAL_SEAL_PATH);
        }

        Phrase signLocation = this.buildPhrase("签章位置", "", hideFont, fontContent);
        document.add(this.buildTable(new float[] {0.4f, 0.6f}, 95, 0, 0, null, signLocation));

    }

    private Phrase buildPhrase(String title, String content, Font titleFont, Font contentFont) {
        Phrase phrase = new Phrase();
        Chunk chunkTitle = new Chunk(title, titleFont);
        Chunk chunkContent = new Chunk(content, contentFont);
        phrase.add(chunkTitle);
        phrase.add(chunkContent);
        return phrase;
    }

    private PdfPCell buildCell(Phrase phrase, int align) {
        PdfPCell pdfPCell = new PdfPCell(phrase);
        // 隐藏单元格周边的上边框
        pdfPCell.disableBorderSide(Rectangle.TOP);
        // 隐藏单元格周边的下边框
        pdfPCell.disableBorderSide(Rectangle.BOTTOM);
        // 隐藏单元格周边的左边框
        pdfPCell.disableBorderSide(Rectangle.LEFT);
        // 隐藏单元格周边的右框
        pdfPCell.disableBorderSide(Rectangle.RIGHT);
        pdfPCell.setHorizontalAlignment(align);
        pdfPCell.setLeading(4, 1);
        return pdfPCell;
    }

    private PdfPTable buildTable(float relativeWidths[], int widthPercentage, int pacingBefore, int pacingAfter,
        Phrase... phrases) {
        return this.buildTable(relativeWidths, widthPercentage, pacingBefore, pacingAfter, Element.ALIGN_LEFT, phrases);
    }

    private PdfPTable buildTable(float relativeWidths[], int widthPercentage, int pacingBefore, int pacingAfter,
        int align, Phrase... phrases) {
        PdfPTable pdfPTable = new PdfPTable(relativeWidths);
        pdfPTable.setWidthPercentage(widthPercentage);
        for (Phrase phrase : phrases) {
            pdfPTable.addCell(this.buildCell(phrase, align));
        }
        pdfPTable.setSpacingBefore(pacingBefore);
        pdfPTable.setSpacingAfter(pacingAfter);
        return pdfPTable;
    }

    @Builder
    @Data
    public static class EmrBO {

        private String emrId;
        private Long patientId;
        private String name;
        private Integer gender;
        private String departmentName;
        /**
         * 年龄:包含单位
         */
        private String age;

        private String ageUnit;

        private Date createdAt;

        /**
         * 主诉
         */
        private String chiefComplaint;

        /**
         * 现病史
         */
        private String presentDisease;
        /**
         * 既往史
         */
        private String pastMedicalHistory;
        /**
         * 体温
         */
        private Double temperature;
        /**
         * 体重
         */
        private Double weight;
        /**
         * 心率
         */
        private Integer heartRate;
        /**
         * 收缩压
         */
        private Integer systolicPressure;
        /**
         * 舒张压
         */
        private Integer diastolicPressure;
        /**
         * 诊断
         */
        private List<String> diagnosis;
        /**
         * 报告图片
         */
        private List<String> reportImgUrls;
        /**
         * 医生
         */
        private String doctorName;
        /**
         * 医院
         */
        private String hospitalName;

        /**
         * 身份证号
         */
        private String idcard;
        /**
         * 出生日期
         */
        private String birthday;
        /**
         * 手机
         */
        private String phone;
        /**
         * 紧急联系人
         */
        private String contact;
        /**
         * 紧急联系人手机
         */
        private String contactPhone;
        /**
         * 地址
         */
        private String address;
        /**
         * 婚姻状态 婚姻状况：0未婚；1已婚
         */
        private Short maritalStatus;

        /**
         * 更多检查结果
         */
        private String moreExamine;

        /**
         * 治疗意见
         */
        private String treatmentOptions;
        /**
         * 月经信息
         */
        private String menstrual;

        /**
         * 阳性体征
         */
        private String positiveSigns;
        /**
         * 必要的阴性体征
         */
        private String negativeSigns;
        /**
         * 过敏史
         */
        private String allergy;
        /**
         * 家庭史
         */
        private String pastFamily;

        /**
         * 病历设置
         */
        private List<MedicalRecordSettingBO> medicalRecordSettingList;
    }
}
